package com.rs;

import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.rs.cache.loaders.ItemDefinitions;
import com.rs.cache.loaders.NPCDefinitions;
import com.rs.cache.loaders.ObjectDefinitions;
import com.rs.cores.CoresManager;
import com.rs.game.Region;
import com.rs.game.World;
import com.rs.game.map.MapBuilder;
import com.rs.game.player.Player;
import com.rs.game.player.content.grandExchange.GrandExchange;
import com.rs.game.tasks.WorldTask;
import com.rs.game.tasks.WorldTasksManager;
import com.rs.net.ServerChannelHandler;
import com.rs.utils.DTRank;
import com.rs.utils.DisplayNames;
import com.rs.utils.IPBanL;
import com.rs.utils.IPMuteL;
import com.rs.utils.Logger;
import com.rs.utils.PkRank;
import com.rs.utils.SerializableFilesManager;
import com.rs.utils.Text;
import com.rs.utils.Utils;

public final class Launcher {

	public static void main(String[] args) throws Exception {
		if (args.length < 3) {
			System.out.println("USE: guimode(boolean) debug(boolean) hosted(boolean)");
			return;
		}
		Settings.HOSTED = Boolean.parseBoolean(args[2]);
		Settings.DEBUG = Boolean.parseBoolean(args[1]);
		long currentTime = Utils.currentTimeMillis();
		Logger.log("Launcher", "Retrieving all essential data, please wait.");
		Initializer.loadFiles();
		System.gc();
		System.runFinalization();
		try {
			Logger.log("Launcher", "Retrieving Server Channel Handler Data.");
			cleanHome();
			ServerChannelHandler.init();
			Text.init();
		} catch (Throwable e) {
			Logger.handle(e);
			Logger.log("Launcher", "Failed in Retrieving Server Channel Handler Data. Aborted launch.");
			System.exit(1);
		}
		long endTime = Utils.currentTimeMillis();
		long totalTime = TimeUnit.MILLISECONDS.toSeconds((endTime - currentTime));
		Logger.log("Launcher", "Succesfully Retrieved All Essential Data In " + totalTime + " second" + (totalTime == 1 ? "" : "s") + "!");
		if (Settings.HOSTED) {
			addAccountsSavingTask();
			addCleanMemoryTask();
			addUpdateGEPricesTask();
			handleMemoryUsage();

		}
	}
	
	public static final boolean restart(String message) {
		return shutdown(message, 3000, true);
	}
	
	public static final boolean shutdown(String message, long delay, final boolean restart) {
		for (Player player : World.getPlayers()) {
			synchronized (player) {
				player.getPackets().sendGameMessage(message);
			}
		}
		ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
		executor.schedule(new Runnable() {
			@Override
			public void run() {
				if (restart) {
					try {
						if (Utils.isWindows()) {
							File file = new File("./~Run.bat");
							Runtime.getRuntime().exec("cmd.exe /C start " + file.getPath());
						} else {
							Runtime.getRuntime().exec("./run.sh");
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				System.exit(1);
			}
		}, delay, TimeUnit.MILLISECONDS);
		return true;
	}

	private static void handleMemoryUsage() {
		DecimalFormat decimalFormat = new DecimalFormat("0.0#%");
		NumberFormat memoryFormat = NumberFormat.getInstance();
		long totalMemory = Runtime.getRuntime().totalMemory();
		long freeMemory = Runtime.getRuntime().freeMemory();
		long usedMemory = totalMemory - freeMemory;
		Logger.log("Total Used Memory: " + memoryFormat.format(usedMemory / 1024)
				+ "/"
				+ memoryFormat.format(totalMemory / 1024)
				+ " MB, "
				+ decimalFormat
				.format(((double) usedMemory / (double) totalMemory)));
		Logger.log("Free Memory: "
				+ memoryFormat.format(freeMemory / 1024) + " MB, " + decimalFormat.format((double) freeMemory / (double) totalMemory));

	}

	private static void addCleanMemoryTask() {
		CoresManager.slowExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				try {
					cleanMemory(Runtime.getRuntime().freeMemory() < Settings.MIN_FREE_MEM_ALLOWED);
				} catch (Throwable e) {
					Logger.handle(e);
				}
			}
		}, 0, 10, TimeUnit.MINUTES);
	}

	private static void addAccountsSavingTask() {
		CoresManager.slowExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				try {
					saveFiles();
				} catch (Throwable e) {
					Logger.handle(e);
				}

			}
		}, 15, 30, TimeUnit.MINUTES);
	}

	public static void cleanMemory(boolean force) {
		if (force) {
			ItemDefinitions.clearItemsDefinitions();
			NPCDefinitions.clearNPCDefinitions();
			ObjectDefinitions.clearObjectDefinitions();
			skip:for (Region region : World.getRegions().values()) {
				for(int regionId : MapBuilder.FORCE_LOAD_REGIONS)
					if(regionId == region.getRegionId())
						continue skip;
				region.unloadMap();
			}
		}
		System.gc();
	}

	public static void cleanHome() {
		WorldTasksManager.schedule(new WorldTask() {
			@Override
			public void run() {
				if (World.getPlayers().size() > 0) {
					//RemoveObjects.removeObjects();
					stop();
				}
			}
		}, 0, 2);
	}

	private Launcher() {

	}

	private static void addUpdateGEPricesTask() {
		Calendar c = Calendar.getInstance();
		c.add(Calendar.DAY_OF_MONTH, 1);
		c.set(Calendar.HOUR, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		int minutes = (int) ((c.getTimeInMillis() - Utils.currentTimeMillis()) / 1000 / 60);
		int halfDay = 12 * 60;
		if (minutes > halfDay)
			minutes -= halfDay;
		CoresManager.slowExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				try {
					GrandExchange.recalcPrices();
				}
				catch (Throwable e) {
					Logger.handle(e);
				}

			}
		}, minutes, halfDay, TimeUnit.MINUTES);
	}

	private static void saveFiles() {
		for (Player player : World.getPlayers()) {
			if (player == null || !player.hasStarted() || player.hasFinished())
				continue;
			SerializableFilesManager.savePlayer(player);
		}
		saveOtherFiles();
		DisplayNames.save();
		IPBanL.save();
		PkRank.save();
		DTRank.save();
	}

	public static void saveOtherFiles() {
		DisplayNames.save();
		IPBanL.save();
		IPMuteL.save();
		PkRank.save();
		DTRank.save();
		GrandExchange.save();
	}

}
